Technical Note TN2107
Working Around Incorrect -needsToDrawRect: Behavior in Custom View Classes

目次

Panther で初めて導入された、NSView の簡易メソッドである -needsToDrawRect: は、いくつかの状況では間違って NO を返します。その結果、コンテンツのうちどの部分を描画すべきかを決定するのに -needsToDrawRect: を使用しているカスタムビュークラスでは、描画が不完全なものになる可能性があります。

このテクニカルノートでは、Panther の間違った動作を回避する方法について説明します。この動作は、Mac OS X の次のメジャーリリースで修正される予定です。

[2004 年 2 月 6 日]



背景

Mac OS X 10.3 では、Cocoa ビュー用に最適化した描画モデルが導入されました。このモデルは新しい NSView メソッドの -getRectsBeingDrawn:count:-needsToDrawRect: によってサポートされており、ビューの描画をコンテンツ領域において更新する必要のある部分に正確に限定することができます。これら新しいメソッドの使用については、「Drawing and Views」のサブトピック、「Constraining Drawing to Improve Performance」で詳しくされています。

-needsToDrawRect: メソッドは、ビューの -drawRect: の実装が、コンテンツ領域内の特定のオブジェクトを描画する必要があるかどうかを調べるための便利な手段として提供されています。-needsToDrawRect: にオブジェクトの境界矩形を渡すと、この矩形が再表示を必要とするビューの一部と重なっている場合は YES が返されます。

先頭に戻る



問題点

効率化のために、NSView の -needsToDrawRect: の実装はまず、渡された矩形が、描画する領域の境界ボックスと重なるかどうかをテストします。場合によっては(ほとんどはビューにテキストを描画する場合)、この境界矩形の計算に間違いが生じ、その結果 -needsToDrawRect: が間違って NO を返し、結果に応じて描画を行う処理が実行されません。

先頭に戻る



-needsToDrawRect: をオーバーライドして正しく実行

この問題を回避して意図したとおりに動作させるには、-needsToDrawRect: を使用するカスタムビュークラスにおいて、-needsToDrawRect: をオーバーライドします。

ビューが一度に多数の矩形を描画するよう要求されることがめったになく、そのコンテンツを描画するのが特に複雑または高価でない場合は、次のような -needsToDrawRect: の再実装で十分です。



リスト 1. シンプルな -needsToDrawRect: の差し替え版

- (BOOL)needsToDrawRect:(NSRect)rect{

    const NSRect *rectList;
    int count;
    int i;
    
    [self getRectsBeingDrawn:&rectList count:&count];    
    for (i = 0; i < count; i++) {
        if (NSIntersectsRect(rect, rectList[i])) {
            return YES;
        }
    }
    return NO;
}



上記のアプローチには、 -needsToDrawRect: を呼び出すコードを変更する必要がないという利点があります。

通常、一度に描画する矩形が多数あるビューの場合にパフォーマンスを改善するには、このメソッドで rectList の境界矩形の外側にあるオブジェクトを迅速に拒否する、別のメソッドを定義して使用することが考えられます。-needsToDrawRect: の代わりに次のメソッドを呼び出し、"rectListBounds" パラメータとして -drawRect: の矩形パラメータを渡します。



リスト 2. より最適化された -needsToDrawRect: の差し替え版

- (BOOL)needsToDrawRect:(NSRect)rect rectListBounds:(NSRect)rectListBounds
{
    const NSRect *rectList;
    int count;
    int i;
    
    if (!NSIntersectsRect(rect, rectListBounds)) {
        return NO;
    }
    [self getRectsBeingDrawn:&rectList count:&count];
    if (count == 1) {
        return YES;
    } else {
        for (i = 0; i < count; i++) {
            if (NSIntersectsRect(rect, rectList[i])) {
                return YES;
            }
        }
        return NO;
    }
}